using System;
using System.Threading;
using System.Collections.Generic;
using System.Runtime.Serialization;
using Kollektiv.Config;
using Kollektiv.Extension;
using System.Linq;
using System.Reflection;

namespace Kollektiv.DB
{
	#region config objects
	[DataContract]
	public class DataServiceConfig
	{
		[DataMember]
		public IEnumerable<DataServiceInstanceConfig> Instances{ get; set; }
	}

    [DataContract]
    public class DataServiceInstanceConfig
    {
        [DataMember(Order = 0)]
        public string ID { get; set; }

        [DataMember(Order = 1)]
        public string DataManagerTypeName { get; set; }

        [DataMember(Order = 2)]
        public string RepositoryAssemblyNames { get; set; }

        [DataMember(Order = 3)]
        public IEnumerable<ConfigValue> Values { get; set; }

        public Type DataManagerType
        {
            get { return this.DataManagerTypeName.FindType(); }
            set { this.DataManagerTypeName = string.Format("{0}, {1}", value.FullName, value.Assembly.GetName().Name); }
        }

        public IEnumerable<Assembly> RepositoryAssemblies
        {
            get
            {
                List<Assembly> assemblyList = new List<Assembly>();

                foreach (string assemblyName in this.RepositoryAssemblyNames.Split(';'))
                {
                    assemblyList.Add(assemblyName.FindAssembly());
                }

                return assemblyList.AsReadOnly();
            }
            set { this.RepositoryAssemblyNames = string.Join(";", value.Select(x => x.GetName().Name)); }
        }
    }

	#endregion
	
	/// <summary>
	/// Service for the persisting data objects.
	/// </summary>
	public class DataService : IDisposable
	{
		private static DataServiceConfig config;
		private static Dictionary<string, DataService> instances;

		static DataService ()
		{
			config = ConfigService.Load<DataServiceConfig> ();
			instances = new Dictionary<string, DataService> ();
		}
		
		/// <summary>
		/// Gets a data manager instance of a given data manager instance configuration for the actual thread.
		/// </summary>
		/// <param name="id">
		/// Identifier of a data manager instance configuration.
		/// </param>
		/// <returns>
		/// Instance of a data manager.
		/// </returns>
		public static DataService GetInstance (string id)
		{
			if (config.Instances.Where (ic => ic.ID == id).Any ()) {
				if (!instances.ContainsKey (id)) {
					DataServiceInstanceConfig instanceConfig = config.Instances.Where (ic => ic.ID == id).First ();
					instances.Add(id, new DataService(instanceConfig, false));
				}
				
				return instances [id];
			}
			
			return null;
		}
		
		/// <summary>
		/// Creates the database schema of a given data manager instance configuration.
		/// </summary>
		/// <param name="id">
		/// Identifier of a data manager configuration.
		/// </param>
		public static void Create (string id)
		{
			DataServiceInstanceConfig instanceConfig = config.Instances.Where (ic => ic.ID == id).First ();
			DataService dataService = new DataService(instanceConfig, true);
			dataService.Dispose();
		}
		
		/// <summary>
		/// Gets the data manager instance configuration identifier.
		/// </summary>
		/// <value>
		/// The identifier.
		/// </value>
		internal string id{ get{return this.instanceConfig.ID;} }

		internal DataServiceInstanceConfig instanceConfig{ get; private set; }
		
		private Dictionary<Thread, DataManager> threadManager = new Dictionary<Thread, DataManager>();
		
		/// <summary>
		/// Gets if there is an active transaction.
		/// </summary>
		/// <value>
		/// <c>true</c> if there is an active transaction, otherwise <c>false</c>.
		/// </value>
		public bool ActiveTransaction{ get{return this.GetManager().ActiveTransaction;} }
		
		private DataService(DataServiceInstanceConfig instanceConfig, bool create)
		{
			this.instanceConfig = instanceConfig;
			
			if(create)
				this.Create();
		}
		
		private void Create()
		{
			if (config.Instances.Where (ic => ic.ID == this.id).Any ()) {
				DataServiceInstanceConfig instanceConfig = config.Instances.Where (ic => ic.ID == id).First ();
				DataManager manager = Activator.CreateInstance (instanceConfig.DataManagerType) as DataManager;
				if (manager != null) {
					manager.Initialize (this, true);
					manager.Dispose ();
				} else
					throw new ConfigException ("error in DataServiceInstanceConfig with ID " + id);
			}
		}
		
		private DataManager GetManager()
		{
			Thread cT = Thread.CurrentThread;
			
			if(!this.threadManager.ContainsKey(cT))
			{
					DataManager manager = Activator.CreateInstance (instanceConfig.DataManagerType) as DataManager;
					if (manager != null) {
						manager.Initialize (this, false);
						this.threadManager.Add (cT, manager);
					} else
						throw new ConfigException ("error in DataServiceInstanceConfig with ID " + id);
			}
			
			return this.threadManager[cT];
		}
		
		/// <summary>
		/// Gets a repository.
		/// </summary>
		/// <returns>
		/// The repository.
		/// </returns>
		/// <typeparam name='T'>
		/// The 1st type parameter.
		/// </typeparam>
		public T GetRepository<T> () where T : DataRepository
		{
			return this.GetManager().GetRepository<T>();
		}
		
		/// <summary>
		/// Begin a transaction.
		/// </summary>
		public void BeginTransaction ()
		{
			this.GetManager().BeginTransaction();
		}
		
		/// <summary>
		/// Commit an active transaction.
		/// </summary>
		public void CommitTransaction ()
		{
			this.GetManager().CommitTransaction();
		}
		
		/// <summary>
		/// Rollback an active transaction.
		/// </summary>
		public void RollbackTransaction ()
		{
			this.GetManager().RollbackTransaction();
		}
		
		/// <summary>
		/// Saves or updates a data object.
		/// </summary>
		/// <param name='obj'>
		/// The affecting data object.
		/// </param>
		public void SaveOrUpdate (DataObject obj)
		{
			this.GetManager().SaveOrUpdate(obj);
		}
		
		/// <summary>
		/// Deletes a data object.
		/// </summary>
		/// <param name='obj'>
		/// The affecting data object.
		/// </param>
		public void Delete (DataObject obj)
		{
			this.GetManager().Delete(obj);
		}
		
		/// <summary>
		/// Gets a query of all data objects of a given type.
		/// </summary>
		/// <typeparam name='T'>
		/// The data object type.
		/// </typeparam>
		public IQueryable<T> Get<T> () where T:DataObject
		{
			return this.GetManager().Get<T>();
		}

		public void Dispose ()
		{
			foreach(Thread t in this.threadManager.Keys)
			{
				this.threadManager[t].Dispose();
			}
		}
	}
}

